use std::collections::HashMap;

use dbus;
use dbus::arg;
use dbus::arg::*;
use dbus::blocking::stdintf::org_freedesktop_dbus::*;
use dbus::blocking::BlockingSender;
use dbus::blocking::Proxy;
use dbus::tree;

use crate::consts::DBUS_BLUEZ_IFACE_ADAPTER;
use crate::DBusRemoteObject;
use crate::Result;

pub trait OrgBluezAdapter1 {
    fn start_discovery(&self) -> Result<()>;
    fn set_discovery_filter(
        &self,
        properties: ::std::collections::HashMap<&str, arg::Variant<Box<dyn arg::RefArg>>>,
    ) -> Result<()>;
    fn stop_discovery(&self) -> Result<()>;
    fn remove_device(&self, device: dbus::Path) -> Result<()>;
    fn address(&self) -> Result<String>;
    fn name(&self) -> Result<String>;
    fn alias(&self) -> Result<String>;
    fn set_alias(&self, value: String) -> Result<()>;
    fn class(&self) -> Result<u32>;
    fn powered(&self) -> Result<bool>;
    fn set_powered(&self, value: bool) -> Result<()>;
    fn discoverable(&self) -> Result<bool>;
    fn set_discoverable(&self, value: bool) -> Result<()>;
    fn discoverable_timeout(&self) -> Result<u32>;
    fn set_discoverable_timeout(&self, value: u32) -> Result<()>;
    fn pairable(&self) -> Result<bool>;
    fn set_pairable(&self, value: bool) -> Result<()>;
    fn pairable_timeout(&self) -> Result<u32>;
    fn set_pairable_timeout(&self, value: u32) -> Result<()>;
    fn discovering(&self) -> Result<bool>;
    fn uuids(&self) -> Result<Vec<String>>;
    fn modalias(&self) -> Result<String>;
}

pub struct OrgBluezAdapter<'a, C> {
    proxy: Proxy<'a, C>,
}

impl<'a, C> OrgBluezAdapter<'a, C> {
    pub fn new(proxy: Proxy<'a, C>) -> Self {
        Self { proxy }
    }
}

impl<'a, T, C> DBusRemoteObject<'a, T, C> for OrgBluezAdapter<'a, C>
where
    T: BlockingSender,
    C: std::ops::Deref<Target = T>,
{
    fn proxy(&self) -> &Proxy<'a, C> {
        &self.proxy
    }

    fn interface(&self) -> &str {
        DBUS_BLUEZ_IFACE_ADAPTER
    }
}

impl<'a, T, C> OrgBluezAdapter1 for OrgBluezAdapter<'a, C>
where
    T: BlockingSender,
    C: std::ops::Deref<Target = T>,
{
    fn start_discovery(&self) -> Result<()> {
        self.method_call("StartDiscoery", ())
    }

    fn set_discovery_filter(
        &self,
        properties: ::std::collections::HashMap<&str, arg::Variant<Box<dyn arg::RefArg>>>,
    ) -> Result<()> {
        self.method_call("SetDiscoveryFilter", (properties,))
    }

    fn stop_discovery(&self) -> Result<()> {
        self.method_call("StopDiscoery", ())
    }

    fn remove_device(&self, device: dbus::Path) -> Result<()> {
        self.method_call("RemoveDevice", (device,))
    }

    fn address(&self) -> Result<String> {
        self.get("Address")
    }

    fn name(&self) -> Result<String> {
        self.get("Name")
    }

    fn alias(&self) -> Result<String> {
        self.get("Alias")
    }

    fn set_alias(&self, value: String) -> Result<()> {
        self.set("Alias", value)
    }

    fn class(&self) -> Result<u32> {
        self.get("Class")
    }

    fn powered(&self) -> Result<bool> {
        self.get("Powered")
    }

    fn set_powered(&self, value: bool) -> Result<()> {
        self.set("Powered", value)
    }

    fn discoverable(&self) -> Result<bool> {
        self.get("Discoverable")
    }

    fn set_discoverable(&self, value: bool) -> Result<()> {
        self.set("Discoverable", value)
    }

    fn discoverable_timeout(&self) -> Result<u32> {
        self.get("DiscoverableTimeout")
    }

    fn set_discoverable_timeout(&self, value: u32) -> Result<()> {
        self.set("DiscoverableTimeout", value)
    }

    fn pairable(&self) -> Result<bool> {
        self.get("Pairable")
    }

    fn set_pairable(&self, value: bool) -> Result<()> {
        self.set("Pairable", value)
    }
    fn pairable_timeout(&self) -> Result<u32> {
        self.get("PairableTimeout")
    }

    fn set_pairable_timeout(&self, value: u32) -> Result<()> {
        self.set("PairableTimeout", value)
    }

    fn discovering(&self) -> Result<bool> {
        self.get("Discovering")
    }

    fn uuids(&self) -> Result<Vec<String>> {
        self.get("UUIDs")
    }

    fn modalias(&self) -> Result<String> {
        self.get("Modalias")
    }
}
